package com.wangjiang.mario.admin.common.base;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wangjiang.mario.admin.common.annotation.DictType;
import com.wangjiang.mario.admin.common.constant.JwtConstant;
import com.wangjiang.mario.admin.common.exception.AlreadyDeleteException;
import com.wangjiang.mario.admin.common.exception.DataScopeException;
import com.wangjiang.mario.admin.common.exception.DictTypeFieldNotFoundException;
import com.wangjiang.mario.admin.common.exception.NoFoundException;
import com.wangjiang.mario.admin.common.object.DictDataObject;
import com.wangjiang.mario.admin.common.object.FieldDictRef;
import com.wangjiang.mario.admin.common.object.UUidList;
import com.wangjiang.mario.admin.common.object.UUidListForBatchUpdate;
import com.wangjiang.mario.admin.common.output.MPage;
import com.wangjiang.mario.admin.common.util.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

public class BaseServiceImpl<M extends BaseMapper<T>, T extends BaseEntity> extends ServiceImpl<M, T>
        implements BaseService<T> {

    private static final String ALREADY_DELETED = "数据已经被删除";
    // 数据字典字段后缀
    private static final String DICT_LABEL = "DictLabel";

    private static final Logger logger = LoggerFactory.getLogger(BaseServiceImpl.class);

    @Autowired
    private HttpServletRequest request;

    /**
     * 新增数据字典字段描述字段的逻辑
     * 在vo类中，某个属性xx是数据字典属性，在vo中再加一个xxDictLabel 属性用来从数据字典中拉取后做呈现
     * 该xxDictLabel属性上面必须含有注解@DictType(xxx) xxx为字典类型
     * 如： 角色表的dataScope 属性， 再加一个指属性如下：
     * <p>
     * ==========================================
     *
     * @param currentPageNumber
     * @param pageSize
     * @param qo
     * @param voClz
     * @return
     * @DictType("data_scope") public String dataScopeDictLabel
     * ==========================================
     */
    @Override
    public MPage<T> basePage(Integer currentPageNumber, Integer pageSize, Object qo, Class<?> voClz) {
        // 以DictLabel结尾的vo字段，将从数据字典中填充数据的label进去，用以显示
        Field[] voFields = voClz.getDeclaredFields();
        // 获取对应的属性值列表和 字典类型列表(用于查询)
        List<FieldDictRef> dictFields = getDictFields(voFields);
        // 这里的map用来做字典类型和属性字段的映射的查找
        Map<String, FieldDictRef> typeFieldMap = dictFields.stream().collect(Collectors.toMap(FieldDictRef::getDictType, ref -> ref));
        // 查询出所有的字典数据属性值 key:字典类型名 value:字典数据列表
        Map<String, List<DictDataObject>> dictDataMap = getDictDataMap(dictFields);

        MPage<T> resultPage = basePage(currentPageNumber, pageSize, qo);
        List newContent = new ArrayList<>();
        try {
            if (resultPage.getContent().size() > 0) {
                for (Object result : resultPage.getContent()) {
                    T cell = (T) result;
                    Object vo = voClz.newInstance();
                    BeanUtils.copyProperties(cell, vo);
                    // 利用反射 将数据字典的值放入到对象里边
                    if (dictDataMap.size() > 0) {
                        for (Map.Entry<String, List<DictDataObject>> entry : dictDataMap.entrySet()) {
                            FieldDictRef ref = typeFieldMap.get(entry.getKey());
                            String fieldName = ref.getDictField();
                            // 原来的属性值
                            Object cellRealData = Reflections.invokeGetter(vo, fieldName);
                            List<DictDataObject> list = entry.getValue();
                            for (DictDataObject data : list) {
                                // 如果相等，则将该属性赋予给该值
                                if (Objects.equals(data.getDictValue(), cellRealData)) {
                                    Reflections.invokeSetter(vo, ref.getCurrentFieldName(), data.getDictLabel());
                                }
                            }
                        }
                    }

                    newContent.add(vo);
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
            throw new RuntimeException("vo转换错误");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("vo转换错误，有私有字段");
        }
        resultPage.setContent(newContent);
        return resultPage;
    }

    /**
     * 根据字段对象 获取对应的字段列表
     *
     * @param dictFields
     * @return
     */
    private Map<String, List<DictDataObject>> getDictDataMap(List<FieldDictRef> dictFields) {
        Map<String, List<DictDataObject>> dictDataMap = new HashMap<>();
        if (dictFields.size() > 0) {
            BaseService dictService = (BaseService) SpringUtil.getBean("systemDictDataServiceImpl");
            Collection<BaseEntity> dictDataList = dictService.baseListByField("dict_type", dictFields.stream().map(FieldDictRef::getDictType).collect(Collectors.toList()));
            // 分组整理到Map中去
            // map的key为dict_type对应的值
            if (dictDataList.size() > 0) {
                for (BaseEntity dictData : dictDataList) {
                    DictDataObject dictDataObject = new DictDataObject();
                    BeanUtils.copyProperties(dictData, dictDataObject);
                    List<DictDataObject> dataList;
                    if (dictDataMap.containsKey(dictDataObject.getDictType())) {
                        dataList = dictDataMap.get(dictDataObject.getDictType());
                    } else {
                        dataList = new ArrayList<>();
                    }
                    dataList.add(dictDataObject);
                    dictDataMap.put(dictDataObject.getDictType(), dataList);
                }
            }
        }
        return dictDataMap;
    }

    /**
     * 将所有的数据字典字段字段返回
     * 比如数据字典字段为 status, 那必须要有 status statusDictLabel 两个字段
     * 但是只返回statusDictLabel 字段
     *
     * @param voFields
     * @return
     */
    private List<FieldDictRef> getDictFields(Field[] voFields) {
        List<FieldDictRef> dictFieldList = new ArrayList<>();
        if (voFields.length > 0) {
            Map<String, Field> cache = new HashMap<>();
            // 先存放到cache
            for (Field field : voFields) {
                cache.put(field.getName(), field);
            }

            for (Field field : voFields) {
                // 如果同时包含数据字典字段和数据字典便签字段
                // 获取该字段头部的DictType注解中的字段描述
                DictType annotation = field.getDeclaredAnnotation(DictType.class);
                // 没有此注解的话，直接忽视掉
                if (CommonUtils.isNull(annotation)) continue;
                String dictFieldString = annotation.value();
                String originFieldName = annotation.field();
                // 这里存放的是数据字典里边的的dict_type字段对应的值
                if (!cache.containsKey(originFieldName)) {
                    throw new DictTypeFieldNotFoundException("字典类型对应的实体属性字段未找到:" + originFieldName);
                }

                FieldDictRef ref = new FieldDictRef();
                ref.setDictField(originFieldName); // 存放属性字段
                ref.setDictType(dictFieldString); // 存放字典类型名
                ref.setCurrentFieldName(field.getName());
                dictFieldList.add(ref);
            }
        }

        return dictFieldList;
    }

    @Override
    public MPage<T> basePage(Integer currentPageNumber, Integer pageSize, Object qo) {
        IPage page = new Page<T>(currentPageNumber, pageSize);
        QueryWrapper<T> selectWrapper;
        try {
            selectWrapper = generateOriginEntityWrapperQo(qo);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("反射获取实体属性错误");
        }
        return pageFormat(new MPage(), page(page, selectWrapper));
    }

    @Override
    public T baseGetByUUid(String uuid) {
        QueryWrapper<T> wrapper = getDelFlagFlaseWrapper();
        wrapper.eq("uuid", uuid);
        return getOne(wrapper);
    }

    protected QueryWrapper<T> getDefaultWrapper() {
        return new QueryWrapper<T>().eq("del_flag", false);
    }

    /**
     * 查询后，返回vo格式的class
     *
     * @param uuid
     * @param clz
     * @return
     */
    @Override
    public Object baseGetByUUid(String uuid, Class<?> clz) {
        T en = baseGetByUUid(uuid);
        if (CommonUtils.isNull(en)) {
            throw new NoFoundException("资源已被删除或不存在");
        }
        Object vo;
        try {
            vo = clz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
            throw new RuntimeException("vo实体实例化错误");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("vo实体实例化错误");
        }

        BeanUtils.copyProperties(en, vo);
        return vo;
    }

    /**
     * 获取一个有delFlag = false 的查询器
     *
     * @return
     */
    private QueryWrapper<T> getDelFlagFlaseWrapper() {
        return new QueryWrapper<T>().eq("del_flag", false);
    }

    /**
     * 设置查询包装器 查询delflag = false 的结果 , 即 未被删除的结果
     *
     * @param selectWrapper
     */
    private void setWrapperDelFlagFalse(QueryWrapper<T> selectWrapper) {
        selectWrapper.eq("del_flag", false);
    }

    /**
     * 根据实体对象生成包装查询类
     *
     * @param qo
     * @return
     */
    private QueryWrapper<T> generateOriginEntityWrapperQo(Object qo) throws IllegalAccessException {
        QueryWrapper<T> wrapper = getDefaultWrapper();
        if (CommonUtils.isNull(qo)) {
            return wrapper;
        }
        Class clz = qo.getClass();
        List<Field> fields = new ArrayList<>(Arrays.asList(clz.getDeclaredFields()));
        if (qo instanceof BaseSortParentEntity) {
            Class bspe = BaseSortParentEntity.class;
            fields.addAll(Arrays.asList(bspe.getDeclaredFields()));
        } else if (qo instanceof BaseSortEntity) {
            Class bse = BaseSortEntity.class;
            fields.addAll(Arrays.asList(bse.getDeclaredFields()));
        } else if (qo instanceof BaseEntity) {
            Class be = BaseEntity.class;
            fields.addAll(Arrays.asList(be.getDeclaredFields()));
        }

        List<String> keyList = new ArrayList<>();
        Map<String, Object> ltCache = new HashMap<>();
        Map<String, Object> gtCache = new HashMap<>();
        Map<String, Object> inCache = new HashMap<>();
        for (Field field : fields) {
            field.setAccessible(true);
            String key = field.getName();
            Object value = field.get(qo);

            // 包装到Wrapper中
            if (CommonUtils.isNotNull(value)) {
                // 解析时间格式
                if (key.endsWith("_start")) {
                    String realKey = key.replace("_start", "");
                    ltCache.put(realKey, value);
                    if (!keyList.contains(realKey)) keyList.add(realKey);
                    continue;
                }
                if (key.endsWith("_end")) {
                    String realKey = key.replace("_end", "");
                    gtCache.put(realKey, value);
                    if (!keyList.contains(realKey)) keyList.add(realKey);
                    continue;
                }

                if (key.endsWith("_in")) {
                    String realKey = key.replace("_in", "");
                    inCache.put(realKey, value);
                    if (!keyList.contains(realKey)) keyList.add(realKey);
                    continue;
                }
                insertToWrapper(wrapper, field.getType(), key, value);
            }
        }

        if (keyList.size() > 0) {
            for (String key : keyList) {
                // between
                if (gtCache.containsKey(key) && ltCache.containsKey(key)) {
                    wrapper.between(StringUtil.underscoreName(key), ltCache.get(key), gtCache.get(key));
                    continue;
                }
                // gt
                if (gtCache.containsKey(key)) {
                    wrapper.gt(StringUtil.underscoreName(key), gtCache.get(key));
                }
                // lt
                if (ltCache.containsKey(key)) {
                    wrapper.gt(StringUtil.underscoreName(key), ltCache.get(key));
                }
                // in 条件
                if (inCache.containsKey(key)) {
                    wrapper.in(StringUtil.underscoreName(key), Arrays.asList(StringUtils.split((String) inCache.get(key), ",")));
                }
            }
        }

        return wrapper;
    }

    /**
     * 将查询类装入查询包装器
     *
     * @param wrapper
     * @param type
     * @param key
     * @param value
     */
    private void insertToWrapper(QueryWrapper<T> wrapper, Class<?> type, String key, Object value) {
        // String 类型用like
        // 基本类型查询处理
        // 如果有数据权限，则直接报错，因为这里是单标查询入口
        if (Objects.equals("dataScope", key)) {
            throw new DataScopeException("通用查询不支持数据权限查询!");
        }
        String realKey = StringUtil.underscoreName(key);
        // 如果有
        if (type.equals(String.class)) {
            wrapper.like(realKey, value);
        } else if (type.equals(Integer.class) || type.equals(Long.class)) {
            wrapper.eq(realKey, value);
        } else if (type.equals(Boolean.class)) {
            wrapper.eq(realKey, value);
        } else {
            // TODO 复杂类型处理
        }
    }


    @Override
    public MPage<T> basePage(Integer currentPageNumber, Integer pageSize, Wrapper<T> selectWrapper) {
        IPage page = new Page<T>(currentPageNumber, pageSize);
        return pageFormat(new MPage(), page(page, selectWrapper));
    }

    protected MPage pageFormat(MPage mPage, IPage page) {
        mPage.setPageSize(page.getSize());
        mPage.setPageNum(page.getCurrent());
        mPage.setTotalNumber(page.getTotal());
        mPage.setTotalPage(page.getPages());
        mPage.setContent(page.getRecords());
        return mPage;
    }

    /**
     * 保存时，获取当前登录账号及对象
     *
     * @param entity 实体对象
     * @return
     */
    @Override
    public boolean baseSave(T entity) {
        // 编辑
        if (CommonUtils.isNotNull(entity.getUuid())) {
            wrapUpdatedEntity(entity);
            return baseUpdateById(entity);
        }
        // 新增
        else {
            wrapCreatedEntity(entity);
            return save(entity);
        }

    }

    @Override
    public boolean baseUpdate(T entity, Wrapper<T> updateWrapper) {
        wrapUpdatedEntity(entity);
        return update(entity, updateWrapper);
    }

    @Override
    public Collection<T> baseList() {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        setWrapperDelFlagFalse(wrapper);
        return list(wrapper);
    }

    @Override
    public Collection<T> baseListByIds(Collection collection) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        setWrapperDelFlagFalse(wrapper);
        wrapper.in("uuid", collection);
        return list(wrapper);
    }

    @Override
    public Collection<T> baseListByField(String field, Collection collection) {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        setWrapperDelFlagFalse(wrapper);
        wrapper.in(field, collection);
        return list(wrapper);
    }

    @Override
    public boolean baseUpdateById(T entity) {
        wrapUpdatedEntity(entity);
        return updateById(entity);
    }

    @Override
    public boolean baseSaveBatch(Collection<T> entityList) {
        wrapCreatedEntityList(entityList);
        return saveBatch(entityList);
    }

    @Override
    public boolean baseUpdateBatch(Collection<T> entityList) {
        wrapUpdatedEntityList(entityList);
        return updateBatchById(entityList);
    }

    @Override
    public boolean baseUpdateBatch(Collection<T> entityList, int batchSize) {
        wrapUpdatedEntityList(entityList);
        return updateBatchById(entityList, batchSize);
    }

    @Override
    public boolean baseSaveBatch(Collection<T> entityList, int batchSize) {
        wrapCreatedEntityList(entityList);
        return saveBatch(entityList, batchSize);
    }

    @Override
    public boolean baseSaveOrUpdateBatch(Collection<T> entityList, int batchSize) {
        if (CommonUtils.isNotNull(entityList) && entityList.size() > 0) {
            for (T entity : entityList) {
                if (CommonUtils.isNotNull(entity.getUuid())) {
                    wrapCreatedEntity(entity);
                } else {
                    wrapUpdatedEntity(entity);
                }
            }
        }
        return saveOrUpdateBatch(entityList, batchSize);
    }

    @Override
    public boolean baseSoftRemoveById(Serializable id) {
        T entity = getById(id);
        if (entity.getDelFlag()) {
            throw new AlreadyDeleteException(ALREADY_DELETED);
        }
        entity.setDelFlag(true);
        wrapUpdatedEntity(entity);
        System.out.println(entity);
        logger.info("remove: {}", entity);
        return updateById(entity);
    }

    @Override
    public boolean baseSoftRemoveByIds(Collection<? extends Serializable> idList) {
        Collection<T> list = baseListByIds(idList);
        if (CommonUtils.isNotNull(list) && list.size() > 0) {
            for (T entity : list) {
                setDelFlag(entity);
            }
        }
        return updateBatchById(list);
    }


    @Override
    public Boolean baseBatchSoftRemoveByUUidList(UUidList uuidList) {
        // 根据list 查询出所有的数据
        // 然后修改删除属性后，批量更新
        List<String> ids = uuidList.getUuidList();
        List<T> list = (List<T>) baseListByIds(ids);
        if (CommonUtils.isNotNull(list)) {
            for (T entity : list) {
                setDelFlag(entity);
            }
        }
        return updateBatchById(list);
    }

    @Override
    public Boolean baseUpdateBatch(UUidListForBatchUpdate<T> uuidListForBatchUpdate) {
        Assert.notEmpty(uuidListForBatchUpdate.getUuidList(), "id列表不能为空");
        List<T> list = (List<T>) baseListByIds(uuidListForBatchUpdate.getUuidList());
        // TODO 基础属性不允许修改 ，比如createdTime createdById等
        if (CommonUtils.isNotNull(list)) {
            List<T> updateList = new ArrayList<>();
            for (T entity : list) {
                T updatedTo = uuidListForBatchUpdate.getUpdatedTo();
                BeanRefUtil.updateBean(entity, updatedTo);
                System.out.println(updatedTo);
                updateList.add(updatedTo);
            }
            return baseUpdateBatch(updateList, 10);
        }
        return false;
    }

    @Override
    public List<T> list(T qo) throws IllegalAccessException {
        QueryWrapper<T> wrapper = generateOriginEntityWrapperQo(qo);
        if (qo instanceof BaseSortParentEntity) {
            wrapper.orderBy(true, true, "tree_level");
        }
        if (qo instanceof BaseSortEntity) {
            wrapper.orderBy(true, true, "tree_sort");
        }
        return list(wrapper);
    }

    @Override
    public List<T> tree(T qo) {
        List<T> list;
        try {
            // 这个已经是全部列表了
            list = list(qo);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            logger.error("查询列表错误");
            throw new RuntimeException("查询列表错误");
        }

        return sortByParentCode(list);
    }


    /**
     * 进行分类分组
     *
     * @param list
     * @return
     */
    private List<T> sortByParentCode(List<T> list) {
        if (CommonUtils.isNotNull(list)) {
            // 最后返回的数组
            List<BaseSortParentEntity> rootList = new ArrayList<>();
            // 子数组缓存map
            Map<String, List<T>> childrenCache = new HashMap<>();
            // 对列表进行归类
            for (T en : list) {
                BaseSortParentEntity entity = (BaseSortParentEntity) en;
                List<T> children;
                // 父类code
                String code = entity.getParentCode();
                // 如果为空，则跳过循环 说明是一级目录
                if (CommonUtils.isNull(code)) {
//                    sortedList.add(entity);
                    rootList.add(entity);
                    continue;
                }
                // 获取子数组缓存中的数组
                // 按照父类数组进行归类
                if (childrenCache.containsKey(code)) {
                    children = childrenCache.get(code);
                } else {
                    children = new ArrayList<>();
                }
                children.add(en);
                childrenCache.put(code, children);
            }
            recursionData(childrenCache, rootList);
            return (List<T>) rootList;
        }
        return list;
    }

    /**
     * 递归封装基础数据
     *
     * @param childrenCache 消耗品，用掉一个少一个，直到用完为止
     * @param rootList
     */
    private void recursionData(Map<String, List<T>> childrenCache, List<BaseSortParentEntity> rootList) {
        // 装配数据进入父子级结构， 装配一程 删掉上一层继续装配
        List<BaseSortParentEntity> cacheRootList = new ArrayList<>();
        for (BaseSortParentEntity en : rootList) {
            if (childrenCache.containsKey(en.getCode())) {
                List children = childrenCache.get(en.getCode());
                // 装配数据
                en.setChildren(children);
                // 数据缓存中删除该数据
                childrenCache.remove(en.getCode());
                // 记录下所有的下级数据，用来做下一次循环的根数据
                cacheRootList.addAll(children);
            }
        }
        // 如果 缓存结果里边仍然有数据,且仍然有下级数据(如果没有就应该断层了),继续循环
        if (CommonUtils.isNotNull(childrenCache) && CommonUtils.isNotNull(cacheRootList)) {
            recursionData(childrenCache, cacheRootList);
        }
    }

    /**
     * 设置实体为删除状态，并添加跟新数据
     *
     * @param entity
     */
    private void setDelFlag(T entity) {
        entity.setDelFlag(true);
        wrapUpdatedEntity(entity);
    }

    private void wrapCreatedEntityList(Collection<T> entityList) {
        if (CommonUtils.isNotNull(entityList) && entityList.size() > 0) {
            for (T entity : entityList) {
                wrapCreatedEntity(entity);
            }
        }
    }

    private void wrapUpdatedEntityList(Collection<T> entityList) {
        if (CommonUtils.isNotNull(entityList) && entityList.size() > 0) {
            for (T entity : entityList) {
                wrapUpdatedEntity(entity);
            }
        }
    }

    private void wrapCreatedEntity(T entity) {
        entity.setCreatedTime(DateUtil.getNowDate());
        entity.setUpdatedTime(DateUtil.getNowDate());
        String token = request.getHeader(JwtConstant.HEADER_TOKEN_AUTH);
        if (CommonUtils.isNotNull(token)) {
            String uuid = JwtUtil.getSubject(token);
            String loginName = (String) JwtUtil.getBody(token).get(JwtConstant.JWT_BODY_LOGINNAME);
            entity.setCreatedById(uuid);
            entity.setCreatedByName(loginName);
            entity.setUpdatedById(uuid);
            entity.setUpdatedByName(loginName);
        } else {
            String uuid = "-1";
            String loginName = "nobody";
            entity.setCreatedById(uuid);
            entity.setCreatedByName(loginName);
            entity.setUpdatedById(uuid);
            entity.setUpdatedByName(loginName);
        }
    }

    private void wrapUpdatedEntity(T entity) {
        entity.setUpdatedTime(DateUtil.getNowDate());
        String token = request.getHeader(JwtConstant.HEADER_TOKEN_AUTH);
        if (CommonUtils.isNotNull(token)) {
            String uuid = JwtUtil.getSubject(token);
            String loginName = (String) JwtUtil.getBody(token).get(JwtConstant.JWT_BODY_LOGINNAME);
            entity.setUpdatedById(uuid);
            entity.setUpdatedByName(loginName);
        } else {
            String uuid = "-1";
            String loginName = "nobody";
            entity.setUpdatedById(uuid);
            entity.setUpdatedByName(loginName);
        }
    }
}
